import sys
import os

from PyQt5.QtWidgets import QDialog, QFileDialog, QMessageBox , QApplication
from PyQt5.QtCore import Qt ,QObject, pyqtSignal, QThread , QByteArray ,QTimer, QDateTime
from PyQt5.QtSerialPort import QSerialPort, QSerialPortInfo

from upload_host_ui import Ui_up_host

from enum import Enum, unique

@unique
class FrameHeadType(Enum):

    MASTER_START_HEADER = 0xAA
    SLAVE_START_HEADER = 0xBB
    MASTER_DATA_HEADER = 0xCC
    SLAVE_DATA_HEADER = 0xDD
    SLAVE_END_HEADER = 0xEE

    SLAVE_ADDR = 0X81
    MASTER_ADDR = 0X01

@unique
class FrameStateType(Enum):

    SLAVE_STA_SUCCEED = 0X00
    SLAVE_RECV_ERROR = 0X01
    SLAVE_STA_TIMEOUT = 0X02
    SLAVE_HEAD_ERROR = 0X03

    MASTER_RECV_ERROR = 0X11
    MASTER_ADDR_ERROR = 0X12

class SerialWorker(QObject):
    data_processed = pyqtSignal(list)  # 信号：处理后的数据
    rx_count_updated = pyqtSignal(int)  # 信号：接收字节计数更新
    status_message = pyqtSignal(str, int)  # 信号：状态消息 (消息, 显示时间)
    state_rx = pyqtSignal(int)  # 信号：接收字节计数更新
    
    def __init__(self):
        super().__init__()
        self.serial_port = None
        self.rx_cnt = 0
        self.SerialBuffer = bytearray()
        self.running = True
        self.last_rx_time = QDateTime.currentDateTime()
        self.port_name = ""
        self.baud_rate = 0
        
        # 初始化定时器，用于检测串口状态
        self.check_timer = QTimer(self)
        self.check_timer.timeout.connect(self.check_serial_status)
        self.check_timer.start(1000)  # 每5秒检测一次

    def open_port(self, port_name, baud_rate):
        """打开串口"""
        self.port_name = port_name
        self.baud_rate = baud_rate
        self.serial_port = QSerialPort()
        self.serial_port.setPortName(port_name)
        self.serial_port.setBaudRate(baud_rate)
        if not self.serial_port.open(QSerialPort.ReadWrite):
            self.status_message.emit("无法打开串口", 4000)
            return False
        
        self.serial_port.readyRead.connect(self.receive_data)
        self.status_message.emit("打开串口成功", 2000)
        self.last_rx_time = QDateTime.currentDateTime()  # 记录最后一次接收数据的时间
        return True

    def close_port(self):
        """关闭串口"""
        if self.serial_port and self.serial_port.isOpen():
            self.serial_port.close()
            self.status_message.emit("关闭串口成功", 2000)
            
    def check_serial_status(self):
        """检测串口状态，如果掉线则尝试重新打开"""
        if not self.running:
            return
            
        # 如果串口未打开或数据接收超时（超过10秒未收到数据），判定为掉线
        current_time = QDateTime.currentDateTime()
        if (not self.serial_port or not self.serial_port.isOpen()) or \
           (self.last_rx_time.msecsTo(current_time) > 10000):
            self.status_message.emit("串口掉线，尝试重新打开...", 3000)
            self.close_port()
            self.open_port(self.port_name, self.baud_rate)

    def stop(self):
        """停止线程"""
        self.running = False
        self.close_port()
        self.check_timer.stop()  # 停止定时器
        
        # 断开所有信号连接
        try:
            self.serial_port.readyRead.disconnect()
        except:
            pass
        
        # 确保线程完全退出
        if hasattr(self, 'thread') and self.thread().isRunning():
            self.thread().quit()
            self.thread().wait()

    def receive_data(self):
        """接收并处理数据"""
        if not self.running:
            return
            
        # 读取新数据并追加到缓冲区
        num = self.serial_port.bytesAvailable()
        if num <= 0:
            return
        
        self.rx_cnt += num
        self.rx_count_updated.emit(self.rx_cnt)
        self.SerialBuffer += self.serial_port.read(num)
        self.last_rx_time = QDateTime.currentDateTime()  # 更新最后一次接收数据的时间
        
        # 边接收边检测帧头
        while len(self.SerialBuffer) >= 4 and self.running:  # 至少要有4字节帧头
            # 查找帧头 0x55 0xaa 0x03 0x08
            header_pos = -1
            for i in range(len(self.SerialBuffer) - 3):
                if (self.SerialBuffer[i] == 0x55 and 
                    self.SerialBuffer[i+1] == 0xAA and 
                    self.SerialBuffer[i+2] == 0x03 and 
                    self.SerialBuffer[i+3] == 0x08):
                    header_pos = i
                    break
            
            if header_pos == -1:  # 未找到帧头，丢弃无效数据
                # 保留最后3字节，因为帧头是4字节，可能后面还有部分帧头
                if len(self.SerialBuffer) > 3:
                    self.SerialBuffer = self.SerialBuffer[-3:]
                else:
                    self.SerialBuffer = bytearray()
                return

            frame_length = 4  # 帧头后面需要772字节
            if len(self.SerialBuffer) < header_pos + 4 + frame_length:
                self.SerialBuffer = self.SerialBuffer[header_pos:]
                return
            else:
                frame_data = self.SerialBuffer[header_pos+4 : header_pos+4+frame_length]
                self.SerialBuffer = self.SerialBuffer[header_pos+4+frame_length:]

                def checksum(data : list):

                    length = len(data)
                    sum_a = 0
                    for i in range ( 0 , length - 2 , 1 ):
                        sum_a += data[i]

                    return sum_a & 0xff

                if  frame_data[0] == FrameHeadType.SLAVE_START_HEADER or \
                    frame_data[0] == FrameHeadType.SLAVE_DATA_HEADER or \
                    frame_data[0] == FrameHeadType.SLAVE_END_HEADER :

                    if frame_data[3] == checksum(frame_data) :
                        if frame_data[1] == FrameHeadType.SLAVE_ADDR :

                            emit_type = (frame_data[0] << 8) | frame_data[2]
                            self.state_rx.emit(emit_type)

                        else:
                            self.state_rx.emit(FrameStateType.MASTER_ADDR_ERROR)

                    else :
                        self.state_rx.emit(FrameStateType.MASTER_RECV_ERROR)

                else:
                    self.state_rx.emit(FrameStateType.SLAVE_HEAD_ERROR)


class DeviceUpgrade(QDialog):
    def __init__(self, parent = None):
        super().__init__(parent)
        self.ui = Ui_up_host()
        self.ui.setupUi(self)

        self.setWindowTitle("设备固件更新")
        self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)

        self.hex_file_path = None
        self.flash_data = None

        self._set_slot()

    def log_message(self , data : str):

        pointer_str = "-> "
        _real_time = QDateTime.currentDateTime().toString(" hh:mm:ss")
        self.ui.tb_dialog.append(f"{_real_time}{pointer_str}{data}")

    def _set_slot(self):

        self.ui.pb_refresh.clicked.connect(self.get_available_ports)
        self.ui.pb_connect.clicked.connect(self.com_control)
        self.ui.pb_download.clicked.connect(self.downlaod)
        self.ui.pb_select_hex.clicked.connect(self.load_hex)

    def com_control(self):
        if self.serial_worker and self.serial_worker.serial_port and self.serial_worker.serial_port.isOpen():
            # 如果串口处于开启状态
            self.close_serial()
            self.ui.pb_connect.setText("连接")
            return
        
        if self.ui.cb_com_list.currentText():
            port_name = self.ui.cb_com_list.currentText()
            baud_rate = 921600
            
            # 创建线程和工作对象
            self.serial_thread = QThread()
            self.serial_worker = SerialWorker()
            self.serial_worker.moveToThread(self.serial_thread)
            
            # 连接信号
            # self.serial_worker.data_processed.connect(self.emit_draw)
            # self.serial_worker.rx_count_updated.connect(self.update_rx_count)
            # self.serial_worker.status_message.connect(self.ui.statusbar.showMessage)
            
            # 当线程开始时，打开串口
            self.serial_thread.started.connect(lambda: self.serial_worker.open_port(port_name, baud_rate))
            
            # 当窗口关闭时，关闭串口和线程
            self.destroyed.connect(self.close_serial)
            
            # 启动线程
            self.serial_thread.start()
            
            self.ui.pb_connect.setText('断开')
        else:
            QMessageBox.warning(self, "警告", "无可用串口", QMessageBox.Yes)

    def close_serial(self):
        """关闭串口和线程"""
        if self.serial_worker:
            self.serial_worker.stop()
        if self.serial_thread:
            self.serial_thread.quit()
            self.serial_thread.wait()
            self.serial_thread = None
            self.serial_worker = None

    def get_available_ports(self):
        # 获取可用的串口
        self._ports = {}  # 用于保存串口的信息
        self.ui.cb_com_list.clear()
        ports = QSerialPortInfo.availablePorts()  # 获取串口信息,返回一个保存串口信息的对象
        if not ports:
            self.log_message("无可用串口")
                                       
            return
        ports.reverse()  # 逆序
        for port in ports:
            # 通过串口名字关联串口变量,并将其添加到界面控件中
            self._ports[port.portName()] = port
            self.ui.cb_com_list.addItem(port.portName())

    def select_hex(self):
        # 设置文件过滤器，只显示.hex文件
        file_filter = "HEX Files (*.hex);;All Files (*)"
        
        # 打开文件选择对话框
        path , filt = QFileDialog.getOpenFileName(
            self,  # 父窗口
            "选择HEX文件",  # 对话框标题
            "",  # 起始目录（空表示当前目录）
            file_filter  # 文件过滤器
        )
        
        # 如果用户选择了文件（点击了"打开"而不是"取消"）
        if path:
            self.hex_file_path = path
            # 可以添加一些额外的处理，比如显示文件路径或验证文件
            print(f"已选择HEX文件: {path}")
            self.log_message(f"已选择HEX文件: {path}")
            return True
        else:
            # 用户取消了选择
            print("文件选择已取消")
            self.log_message("文件选择已取消")
            return False
        
    def load_hex(self):

        if self.select_hex() :
            self.parse_hex_file()

        else:
            pass
    
    def parse_hex_file(self):
        """解析HEX文件并按256字节分组"""
        try:
            # 初始化变量
            self.hex_data = {}  # 存储解析后的数据 {地址: 字节值}
            self.pages = {}     # 存储分页后的数据 {页基地址: [256字节数据]}
            extended_address = 0  # 扩展地址（用于32位地址）
            
            with open(self.hex_file_path, 'r') as f:
                for line_num, line in enumerate(f, 1):
                    line = line.strip()
                    if not line.startswith(':'):
                        continue
                    
                    # 解析HEX记录
                    try:
                        byte_count = int(line[1:3], 16)
                        address = int(line[3:7], 16)
                        record_type = int(line[7:9], 16)
                        data_str = line[9:9+byte_count*2]
                        
                        if record_type == 0x00:  # 数据记录
                            # 计算完整地址（包括扩展地址）
                            full_address = extended_address + address
                            self.process_data_record(full_address, data_str, byte_count)
                            
                        elif record_type == 0x01:  # 文件结束记录
                            break
                            
                        elif record_type == 0x04:  # 扩展线性地址记录
                            # 更新扩展地址（左移16位）
                            extended_address = int(data_str, 16) << 16
                            
                    except ValueError as e:
                        print(f"第{line_num}行解析错误: {e}")
                        continue
            
            # 按256字节分组
            self.group_into_pages()
            
            # 显示解析结果
            self.display_hex_data()
            
            # 保存分页数据到文件夹
            self.save_pages_to_folder()
            
            print(f"解析完成: 共{len(self.hex_data)}字节数据，{len(self.pages)}个页")
            
        except Exception as e:
            print(f"解析HEX文件时出错: {e}")
            # 可以在这里添加错误提示框
            # QMessageBox.critical(self, "错误", f"解析HEX文件时出错:\n{str(e)}")

    def save_pages_to_folder(self, folder_name="test_hex_out"):
        """将整个HEX文件数据保存为单个bin、hex和txt文件"""
        try:
            # 获取当前工作目录并创建输出文件夹路径
            current_dir = os.getcwd()
            output_dir = os.path.join(current_dir, folder_name)
            
            # 如果文件夹已存在，不删除直接使用；不存在则创建
            if os.path.exists(output_dir):
                pass
            else:
                os.makedirs(output_dir)
            
            # 获取完整的地址范围
            if not self.hex_data:
                print("没有数据可保存")
                return
                
            # 获取最小和最大地址
            all_addresses = list(self.hex_data.keys())
            min_addr = min(all_addresses)
            max_addr = max(all_addresses)
            
            # 确保数据块大小是256字节的整数倍
            data_size = max_addr - min_addr + 1
            padded_size = ((data_size + 255) // 256) * 256  # 向上取整到256的倍数
            
            # 创建连续的数据块，填充到256字节的整数倍
            continuous_data = [0xFF] * padded_size
            
            # 填充数据
            for addr, value in self.hex_data.items():
                offset = addr - min_addr
                if 0 <= offset < padded_size:
                    continuous_data[offset] = value

            print(continuous_data)
            
            # 生成文件名（基于原始HEX文件名）
            base_name = os.path.splitext(os.path.basename(self.hex_file_path))[0]
            bin_filename = os.path.join(output_dir, f"{base_name}.bin")
            txt_filename = os.path.join(output_dir, f"{base_name}.txt")
            hex_filename = os.path.join(output_dir, f"{base_name}.hex")
            
            # 保存为二进制文件
            with open(bin_filename, 'wb') as f:
                f.write(bytes(continuous_data))
            
            # 保存为文本文件（十六进制格式）
            with open(txt_filename, 'w') as f:
                # 写入文件信息
                f.write(f"HEX文件解析结果\n")
                f.write("=" * 60 + "\n")
                f.write(f"源文件: {os.path.basename(self.hex_file_path)}\n")
                f.write(f"起始地址: 0x{min_addr:08X}\n")
                f.write(f"结束地址: 0x{max_addr:08X}\n")
                f.write(f"数据大小: {data_size} 字节 (填充到 {padded_size} 字节)\n")
                f.write(f"页数: {len(self.pages)}\n\n")
                
                # 写入页信息表
                f.write("页信息:\n")
                f.write("-" * 50 + "\n")
                f.write("页基地址      有效字节数/总字节数\n")
                f.write("-" * 50 + "\n")
                
                for page_base in sorted(self.pages.keys()):
                    page_data = self.pages[page_base]
                    valid_bytes = sum(1 for b in page_data if b != 0xFF)
                    f.write(f"0x{page_base:08X}    {valid_bytes:3d}/256\n")
                
                f.write("\n" + "=" * 60 + "\n\n")
                
                # 写入十六进制数据（每行16字节）
                for i in range(0, padded_size, 16):
                    # 行地址
                    line_addr = min_addr + i
                    f.write(f"{line_addr:08X}: ")
                    
                    # 十六进制数据
                    hex_line = ""
                    for j in range(16):
                        if i + j < padded_size:
                            hex_line += f"{continuous_data[i+j]:02X} "
                        else:
                            hex_line += "   "  # 保持对齐
                    f.write(f"{hex_line:<47}")  # 对齐
                    
                    # ASCII表示（可打印字符）
                    ascii_line = ""
                    for j in range(16):
                        if i + j < padded_size:
                            byte_val = continuous_data[i+j]
                            if 32 <= byte_val <= 126:
                                ascii_line += chr(byte_val)
                            else:
                                ascii_line += "."
                        else:
                            ascii_line += " "
                    f.write(f" {ascii_line}\n")
            
            # 保存为Intel HEX格式文件
            self.save_as_hex(hex_filename, min_addr, continuous_data)
            
            print(f"HEX文件数据已保存到 {output_dir} 文件夹")
            print(f"生成的文件:")
            print(f"  - {bin_filename} (二进制格式)")
            print(f"  - {txt_filename} (文本格式)")
            print(f"  - {hex_filename} (HEX格式)")
            
        except Exception as e:
            print(f"保存数据时出错: {e}")

    def save_as_hex(self, filename, base_address, data):
        """将整个数据保存为Intel HEX格式"""
        with open(filename, 'w') as f:
            # 计算有效数据的起始和结束位置
            start_idx = 0
            end_idx = len(data) - 1
            
            # 跳过开头的0xFF
            while start_idx < len(data) and data[start_idx] == 0xFF:
                start_idx += 1
            
            # 跳过结尾的0xFF
            while end_idx > start_idx and data[end_idx] == 0xFF:
                end_idx -= 1
            
            # 如果没有有效数据，只写入结束记录
            if start_idx > end_idx:
                f.write(":00000001FF\n")
                return
            
            # 写入扩展地址记录（如果需要）
            if base_address + start_idx > 0xFFFF:
                ext_addr = (base_address + start_idx) >> 16
                checksum = 0x02 + 0x00 + 0x00 + 0x04  # 长度+地址+类型
                checksum += (ext_addr >> 8) & 0xFF
                checksum += ext_addr & 0xFF
                checksum = (-checksum) & 0xFF
                f.write(f":02000004{ext_addr:04X}{checksum:02X}\n")
            
            # 写入数据记录
            current_addr = base_address + start_idx
            remaining_data = end_idx - start_idx + 1
            data_index = start_idx
            
            while remaining_data > 0:
                # 每行最多16字节
                record_len = min(16, remaining_data)
                
                # 检查是否需要地址扩展
                if current_addr > 0xFFFF:
                    ext_addr = current_addr >> 16
                    checksum = 0x02 + 0x00 + 0x00 + 0x04  # 长度+地址+类型
                    checksum += (ext_addr >> 8) & 0xFF
                    checksum += ext_addr & 0xFF
                    checksum = (-checksum) & 0xFF
                    f.write(f":02000004{ext_addr:04X}{checksum:02X}\n")
                
                # 构建数据字符串
                data_hex = ""
                addr_low = current_addr & 0xFFFF
                checksum = record_len + (addr_low >> 8) + (addr_low & 0xFF)
                
                for i in range(record_len):
                    byte_val = data[data_index + i]
                    data_hex += f"{byte_val:02X}"
                    checksum += byte_val
                
                # 计算校验和
                checksum = (-checksum) & 0xFF
                
                # 写入记录
                f.write(f":{record_len:02X}{addr_low:04X}00{data_hex}{checksum:02X}\n")
                
                # 更新指针
                current_addr += record_len
                data_index += record_len
                remaining_data -= record_len
            
            # 写入结束记录
            f.write(":00000001FF\n")

    def process_data_record(self, base_address, data_str, byte_count):
        """处理数据记录，将数据存入字典"""
        for i in range(byte_count):
            byte_addr = base_address + i
            byte_value = int(data_str[i*2:i*2+2], 16)
            self.hex_data[byte_addr] = byte_value

    def group_into_pages(self, page_size=256):
        """将数据按页大小分组，确保每个page都是256字节"""
        if not self.hex_data:
            return
        
        # 获取地址范围
        addresses = list(self.hex_data.keys())
        min_addr = min(addresses)
        max_addr = max(addresses)
        
        # 计算页边界
        start_page = (min_addr // page_size) * page_size
        end_page = ((max_addr + page_size) // page_size) * page_size
        
        # 创建页数据
        for page_base in range(start_page, end_page, page_size):
            page_data = []
            for offset in range(page_size):
                addr = page_base + offset
                if addr in self.hex_data:
                    page_data.append(self.hex_data[addr])
                else:
                    page_data.append(0xFF)  # 用0xFF填充空白区域
            
            # 确保每个page都是256字节
            if len(page_data) < page_size:
                page_data.extend([0xFF] * (page_size - len(page_data)))
            
            self.pages[page_base] = page_data

    def display_hex_data(self):
        """显示解析后的数据（可根据需要自定义显示方式）"""
        # 示例：打印页信息
        for page_base, data in self.pages.items():
            # 计算该页中实际有数据的字节数
            valid_bytes = sum(1 for byte in data if byte != 0xFF)
            print(f"Page 0x{page_base:08X}: {valid_bytes}/{len(data)} 字节有效数据")

    def downlaod(self):
        pass

if __name__ == "__main__":
    app = QApplication(sys.argv)
    
    window = DeviceUpgrade()
    window.show()
    
    sys.exit(app.exec_())
        
        